package com.edol.painting.utils;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.RectF;
import android.view.MotionEvent;

import com.doodle.common.utils.GestureRecognizer;

public class ViewerGestureListener implements GestureRecognizer.Listener {
	
	private static final float MAX_SCALE = 3.0f;
	
	private final int mWidth;
	private final int mHeight;
	
	private final ViewRectChangedListener mListener;
	
	private final float mCenterX;
	private final float mCenterY;
	
	private float mViewLeft;
	private float mViewTop;
	private float mViewRight;
	private float mViewBottom;
	
    private float mScale;
    private float mTranslateX;
    private float mTranslateY;
    private float mInverseScale;
    
	private RectF mDstRect;
	
	private float mLastScale;
	private float mBeginScale;
	private float mScaleBase;
	
	private float mStartFocusX;
	private float mStartFocusY;
	
	private ValueHolder mValueHolder;
	private ValueAnimator mAnimator;
	
	public interface ViewRectChangedListener {
		void onViewRectChanged();
	}
	
	public ViewerGestureListener(int width, int height, ViewRectChangedListener listener) {
		mWidth = width;
		mHeight = height;
		
		mListener = listener;
		
		mCenterX = mWidth * 0.5f;
		mCenterY = mHeight * 0.5f;
		
		mViewLeft = mWidth * 0.2f;
		mViewTop = mViewLeft;
		mViewRight = mWidth * 0.8f;
		mViewBottom = mHeight * 0.5f;
		
		mTranslateX = 0;
	    mTranslateY = 0;
	    mScale = 1.0f;
	    mInverseScale = 1.0f;
	    
	    mDstRect = new RectF();
		
	    calculateDstRect();
	    
	    mValueHolder = new ValueHolder();
	    mAnimator = ObjectAnimator.ofFloat(mValueHolder, "ratio", 0, 1.0f);
	}
	
	private void calculateDstRect() {
		float left = transformX(0);
		float top = transformY(0);
		float right = transformX(mWidth);
		float bottom = transformY(mHeight);
		mDstRect.set(left, top, right, bottom);
	}
	
	public float getTranslateX() {
		return mTranslateX;
	}
	
	public float getTranslateY() {
		return mTranslateY;
	}
	
	public float getScale() {
		return mScale;
	}
	
	public float getInverseScale() {
		return mInverseScale;
	}
	
	public float transformX(float x) {
		return (x - mCenterX) * mScale + mCenterX + mTranslateX;
	}
	
	public float transformY(float y) {
		return (y - mCenterY) * mScale + mCenterY + mTranslateY;
	}
	
	public float inverseX(float transformedX) {
		return (transformedX - mTranslateX - mCenterX) * mInverseScale  + mCenterX;
	}
	
	public float inverseY(float transformedY) {
		return (transformedY - mTranslateY - mCenterY) * mInverseScale + mCenterY;
	}
	
	public RectF getDstRect() {
		return mDstRect;
	}
	
	public void setViewCenter(float centerX, float centerY) {
		mViewLeft = centerX * 0.4f;
		mViewTop = mViewLeft;
		mViewRight = centerX * 2 - mViewLeft;
		mViewBottom = centerY;
	}
	
	public boolean onBackPressed() {
		if (mScale > 1.0f || Math.abs(mTranslateX) > 1 || Math.abs(mTranslateY) > 1) {
			mAnimator.cancel();
			mValueHolder.init(mScale, mTranslateX, mTranslateY, 1.0f, 0, 0);
			mAnimator.start();
			return true;
		}
		return false;
	}

	@Override
	public boolean onSingleTapUp(float x, float y) {
		return false;
	}

	@Override
	public boolean onSingleTapUpConfirmed(float x, float y) {
		return false;
	}

	@Override
	public boolean onDoubleTap(float x, float y) {
		return false;
	}

	@Override
	public boolean onScroll(float dx, float dy, float totalX, float totalY) {
		return false;
	}

	@Override
	public boolean onFling(float velocityX, float velocityY) {
		return false;
	}

	@Override
	public boolean onScaleBegin(float focusX, float focusY) {
		mAnimator.cancel();
		mLastScale = 1.0f;
		mBeginScale = mScale;
		mScaleBase = 1.0f;
		mStartFocusX = inverseX(focusX);
		mStartFocusY = inverseY(focusY);
		return true;
	}

	@Override
	public boolean onScale(float focusX, float focusY, float scale) {
		if (mScale < 5) {
			float focusXDiff = focusX - transformX(mStartFocusX);
			float focusYDiff = focusY - transformY(mStartFocusY);
			
			mTranslateX += focusXDiff * 0.6f;
			mTranslateY += focusYDiff * 0.6f;
			
			scale = scale * 0.6f + mLastScale * 0.4f;
			mLastScale = scale;
			mScale = mBeginScale * scale / mScaleBase;
			mInverseScale = 1.0f / mScale;
			
			calculateDstRect();
			mListener.onViewRectChanged();
		}
		return false;
	}

	@Override
	public boolean onDoubleTapEvent(MotionEvent e) {
		return false;
	}

	@Override
	public void onScaleEnd() {
		boolean scaleAnim = false;
		boolean translateAnim = false;
		
		float scale = mScale;
		float translateX = mTranslateX;
		float translateY = mTranslateY;
		
		boolean panning = Math.abs(mScale / mBeginScale - 1.0f) <= 0.1f;
		
		if (!panning && mScale < 0.9f) {
			mAnimator.cancel();
			mValueHolder.init(mScale, mTranslateX, mTranslateY, 1, 0, 0);
			mAnimator.start();
			return;
		} else if (mScale < 1) {
			scaleAnim = true;
			scale = 1;
		} else if (mScale > MAX_SCALE) {
			mAnimator.cancel();
			float scaleDiff = mScale - MAX_SCALE;
			float diff = mStartFocusX - mCenterX;
			float translateXDiff = diff * scaleDiff;
			diff = mStartFocusY - mCenterY;
			float translateYDiff = diff * scaleDiff;
			mValueHolder.init(mScale, mTranslateX, mTranslateY, MAX_SCALE, mTranslateX + translateXDiff, mTranslateY + translateYDiff);
			mAnimator.start();
			return;
		}
		
		if (mDstRect.left > mViewLeft) {
			translateAnim = true;
			translateX = mViewLeft + (mWidth * scale - mWidth) * 0.5f;
		}
		if (mDstRect.top > mViewTop) {
			translateAnim = true;
			translateY = mViewTop + (mHeight * scale - mHeight) * 0.5f;
		}
		if (mDstRect.right < mViewRight) {
			translateAnim = true;
			translateX = mViewRight - mWidth - (mWidth * scale - mWidth) * 0.5f;
		}
		if (mDstRect.bottom < mViewBottom) {
			translateAnim = true;
			translateY = mViewBottom - mHeight - (mHeight * scale - mHeight) * 0.5f;
		}
		
		if (scaleAnim || translateAnim) {
			mAnimator.cancel();
			mValueHolder.init(mScale, mTranslateX, mTranslateY, scale, translateX, translateY);
			mAnimator.start();
		}
	}

	@Override
	public void onDown(float x, float y) {
		
	}

	@Override
	public void onUp() {
		
	}

	@Override
	public void onLongPress(float x, float y) {
		
	}
	
	private class ValueHolder {
		
		private float mRatio;
		
		private float mStartScale;
		private float mStartTranslateX;
		private float mStartTranslateY;
		private float mTargetScale;
		private float mTargetTranslateX;
		private float mTargetTranslateY;
		
		public void init(float startScale, float startTranslateX, float startTranslateY,
				float targetScale, float targetTranslateX, float targetTranslateY) {
			mStartScale = startScale;
			mStartTranslateX = startTranslateX;
			mStartTranslateY = startTranslateY;
			mTargetScale = targetScale;
			mTargetTranslateX = targetTranslateX;
			mTargetTranslateY = targetTranslateY;
		}
		
		@SuppressWarnings("unused")
		public void setRatio(float ratio) {
			mRatio = ratio;
			float inverseRatio = 1 - mRatio;
			mScale = mStartScale * inverseRatio + mTargetScale * mRatio;
			mInverseScale = 1.0f / mScale;
			mTranslateX = mStartTranslateX * inverseRatio + mTargetTranslateX * mRatio;
			mTranslateY = mStartTranslateY * inverseRatio + mTargetTranslateY * mRatio;
			calculateDstRect();
			mListener.onViewRectChanged();
		}
		
		@SuppressWarnings("unused")
		public float getRatio() {
			return mRatio;
		}
	}
}
